<?php
/**************************************************************************
 *
 *   Copyright 2010 American Public Media Group
 *
 *   This file is part of AIR2.
 *
 *   AIR2 is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   AIR2 is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with AIR2.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************/

require_once 'AIR2_CRUD_Controller.php';
require_once 'tank/CSVExporter.php';

/**
 * Batch API
 *
 * POST   air2/batch : Create new batch
 * GET    air2/batch : List batches that either belong to you, or are public
 *   @param q :
 *   @param self :
 *   @param deleted :
 *   @param archived :
 *   @param type :
 *
 * GET    air2/batch/1234 : Read single batch
 * PUT    air2/batch/1234 : Modify single batch (Must own)
 * DELETE air2/batch/1234 : Delete single batch (Must own)
 *
 * GET    air2/batch/1234/contents : List contents of a batch
 *   @param q :
 * PUT    air2/batch/1234/contents : Modify contents of a batch
 *   @param add : list of items to add, with fields 'uuid' and 'type'
 *   @param remove : list of items to remove
 *   @param removeall : list of exclusions from removing all items
 *   @param merge : list of other batch uuid's to copy contents into this batch
 *   @param addsearch : search query to run and add all results to batch. Must
 *          include: 'q', 'i', and 'total'.
 *   @param random : create random batches from this batch. Must define either
 *          'numbatches', 'size', or both.  'numbatches' defines how many
 *          batches you want to create.  'size' defines how many items are in
 *          each batch.  Specifying only 'numbatches' splits the batch as evenly
 *          as possible into that many non-intersecting batches.  Specifying
 *          only 'size' will create 1 batch of a certain size.  Specifying both
 *          will give you a number of non-intersecting batches of size 'size'.
 *          This throws an error if (num*size > total).
 *
 * @author rcavis
 * @package default
 */
class Batch_Controller extends AIR2_CRUD_Controller {
    public $base_model_name = 'Batch';
    public $base_view_name  = 'batch';
    public $exposed_relations = array(
        'contents' => 'BatchItem',
    );
    public $autoload_relations = array();
    public $column_blacklist = array('bitem_xid');
    public $column_whitelist = array();
    public $taggable = false;
    public $unique_name_col = 'batch_name';


    /**
     * Add custom relations to queries to this controller
     *
     * @param AIR2_Query $q
     * @param boolean $deeply (optional)
     */
    protected function _add_query_load_related(&$q, $deeply=true) {
        if (!$this->base_uuid) {
            $this->airbatch->add_batch_args($q, $this->query_args);
            $this->airbatch->add_batch_query_parts($q);
            $this->extra_field_defs = $this->airbatch->extra_fields;
        }
        elseif (!$this->rel_uri) {
            $this->airbatch->add_batch_query_parts($q);
            $this->extra_field_defs = $this->airbatch->extra_fields;
        }
        elseif ($this->rel_uri == 'contents') {
            // create the correct "from" statement, based on batch_type
            $batch = $this->_find_base();
            $this->airbatch->add_content_args($batch, $q, $this->query_args);
            $this->airbatch->add_content_query_parts($q, $batch->batch_type);
        }

        parent::_add_query_load_related($q, $deeply);
    }


    /**
     * Add default sorting to a query
     *
     * @param string  $uuid     (optional)
     * @param string  $rel_uri  (optional)
     * @param string  $rel_uuid (optional)
     * @param array   $params   (optional)
     * @return AIR2_Query
     */
    public function build_query($uuid=null, $rel_uri=null, $rel_uuid=null, $params=null) {
        $q = parent::build_query($uuid, $rel_uri, $rel_uuid, $params);
        if (!$uuid && !isset($params['u'])) {
            $this->airbatch->add_batch_default_sort($q);
        }
        elseif ($rel_uri == 'contents') {
            $this->airbatch->add_content_default_sort($q);
        }
        return $q;
    }


    /**
     * Override to add custom update queries
     *
     * @param Doctrine_Record $record (reference) the record object to modify
     * @param associative-array $data   the radix data to update the record with
     */
    protected function _update_record(&$record, $data) {
        if (!$this->rel_uri) {
            if ($this->request_method == 'POST') {
                $record->batch_user_id = AIR2_REMOTE_USER_ID;
                $record->batch_status = 'A';
            }
            elseif ($this->request_method == 'PUT') {
                if (isset($data['batch_type'])) {
                    $this->success = false;
                    $this->message = "Cannot change 'batch_type' of an existing batch!";
                    $this->crud_response();
                }
                unset($data['batch_user_id']); //don't EVER change batch owner!
            }
        }

        parent::_update_record($record, $data);
    }


    /**
     * Override POST related (invalid for batches)
     *
     * @param string  $uuid
     * @param string  $rel_uri
     */
    public function crud_POST_related($uuid, $rel_uri) {
        show_error('Method not allowed', 405);
    }


    /**
     * Custom accessor (defined in config/routes.php) to allow PUT operations
     * on batch/<uuid>/contents.  This is used for all bulk-modifications on
     * batch contents.
     *
     * @param string  $uuid
     * @param string  $rel_uri only 'contents' is valid
     */
    public function PUT_batch_contents($uuid, $rel_uri) {
        if ($rel_uri != 'contents') {
            show_error('Method not allowed', 405);
        }

        // make sure this base uuid is valid
        $this->base_uuid = $uuid;
        $batch = $this->_find_base();

        // sanity checks
        if (!isset(Batch::$TYPES[$batch->batch_type])) {
            $this->success = false;
            $this->message = 'Unable to add items to unknown batch type: "'.
                $batch->batch_type.'"';
            $this->crud_response();
            return;
        }

        // get operations to alter the batch contents
        $add_data = $this->_decode_input('add', false);
        $remove_data = $this->_decode_input('remove', false);
        $rmvall_data = $this->_decode_input('removeall', false);
        $merge_data = $this->_decode_input('merge', false);
        $search_data = $this->_decode_input('addsearch', false);
        $random_data = $this->_decode_input('random', false);

        // security checks - only need reader-access to create random batch
        $this_user = $this->airuser->get_user();
        if ($random_data) {
            if (!$batch->user_may_read($this_user)) $this->show_403();
        }
        else {
            if (!$batch->user_may_write($this_user)) $this->show_403();
        }

        // random operation processes ONLY by itself
        if ($random_data && is_array($random_data)) {
            if ($add_data || $remove_data || $rmvall_data || $merge_data || $search_data) {
                $this->success = false;
                $this->message = 'Error! Random batch operation must run alone!';
                $this->crud_response(); //exits
            }
            $num = isset($random_data['numbatches']) ? $random_data['numbatches'] : null;
            $size = isset($random_data['size']) ? $random_data['size'] : null;
            $rand_ids = $batch->random_output($num, $size);

            //return a list of the generated batches, NOT the original batch!!
            $q = $this->build_query();
            $q->whereIn('batch_id', $rand_ids);
            $data = $this->fetch_data($q, false, true); //return list
            $this->crud_response($data); //exits!
        }

        // process all other operations together
        $add_counts = array();
        try {
            if ($add_data && is_array($add_data))
                $add_counts = $batch->add_items($add_data);
            if ($remove_data && is_array($remove_data))
                $batch->remove_items($remove_data);
            if ($rmvall_data)
                $batch->remove_all($rmvall_data);
            if ($merge_data && is_array($merge_data))
                $batch->merge($merge_data);
            if ($search_data && is_array($search_data))
                $add_counts = $batch->add_search($search_data);
        }
        catch (Exception $e) {
            $this->success = false;
            $this->message = $e->getMessage();
            $this->crud_response();
        }

        // return the batch object (with updated batch_count)
        $q = $this->build_query($uuid);
        $data = $this->fetch_data($q, false); //no metadata
        $data = array_merge($data, $add_counts);
        $this->crud_response($data);
    }


    /**
     * Limit the name list to those owned by this user
     *
     * @return AIR2_Query
     */
    protected function get_name_query() {
        $q = parent::get_name_query();
        //$q->addWhere('batch_user_id = ?', AIR2_REMOTE_USER_ID); // TODO doesn't query_may_read() handle this?
        return $q;
    }


    /**
     * Hijack data fetching for csv views pointing at 'contents'.
     *
     * @param AIR2_Query $q
     * @param boolean $add_meta
     * @param boolean $return_list
     * @return array
     */
    public function fetch_data(&$q, $add_meta=true, $return_list=false) {
        if ($this->airoutput->get_output_view() == 'csv' && $this->rel_uri == 'contents') {
            $q = Doctrine_Query::create()->from('Source a');

            // batch sources
            $bid = $this->_find_base()->batch_id;
            $bsrc = 'select bitem_xid from batch_item where bitem_batch_id = ? '.
                'and bitem_type = ?';
            $q->addWhere("a.src_id in ($bsrc)", array($bid, Batch::$TYPE_SOURCE));

            // join a bunch of stuff
            $q->leftJoin('a.SrcAlias sa');
            $q->leftJoin('a.SrcMailAddress sm with sm.smadd_primary_flag = true');
            $q->leftJoin('a.SrcPhoneNumber sp with sp.sph_primary_flag = true');
            $q->leftJoin('a.SrcEmail se with se.sem_primary_flag = true');
            $q->leftJoin('a.SrcFact sf');
            $data = $q->fetchArray();

            $export = new CSVExporter();
            return array(
                'filename' => air2_urlify($this->_find_base()->batch_name).'.csv',
                'radix' => $export->format_data($data),
                'header' => $export->get_headers(),
            );
        }
        else {
            return parent::fetch_data($q, $add_meta, $return_list);
        }
    }


}
